/*!
 * 通用表单校验
 * zhh.validate.js
 * https://gitee.com/zhaohuihua/zhh.validate.js
 * https://zhaohuihua.gitee.io/project/web/zhh.validate.js/doc/index.html
 * 赵卉华 / zhaohuihua@126.com
 */
/**
 * v2.0 / 2016-10-08
 */
(function($) {
	"use strict";
	$.fn.zhhvalidate = function(type) { // 校验
		var args = arguments;
		if (typeof(type) == "string") {
			if (type == "init") {
				return this.each(function() {
					init.apply(this, args);
				});
			} else {
				var method = zv.methods[type];
				if (!method) {
					$.log.error("$.fn.zhhvalidate.defaults.methods." + type + " not found!");
					return this;
				}
				return method.apply(this, Array.prototype.splice.call(args, 1));
			}
		} else {
			return this.each(function(i) {
				var me = $(this);
				if(me.is("form")) {
					vldform.apply(this, args);
				} else if (me.is(zv.selectors.line.join(","))) {
					vldline.apply(this, args);
				} else {
					vldinput.apply(this, args);
				}
			});
		}
	};

	var rules = (function() {
		var map = {};
		var exist = function(name) {
			return name && name in map;
		};
		// 获取规则
		var get = function(name) {
			return map[name];
		};
		var naming = /^[\w]+([\-\.][\w]+)*$/;
		// 新增或修改规则
		var put = function(name, fn) {
			if (!name) {
				throw "rule name is undefined.";
			} else if (/^\./.test(name)) {
				throw "rule name " + name + " cannot begin with a dot.";
			} else if (!naming.test(name)) {
				throw "rule name " + name + " does not conform to naming conventions.";
			} else if (!$.isFunction(fn)) {
				throw "rule handler isn't a function.";
			}
			map[name] = fn;
		};
		// 删除规则
		var remove = function(name) {
			delete map[name];
		};
		return { exist:exist, get:get, put:put, remove:remove }
	})();


	var init = function(options) {
		var form = $(this);
		if(!form.is("form")) { return; }
		var formOpts = $.extend(true, {}, form.zoptions("vld"), options);
		if (formOpts.init == false) { return; }
		var namespace = ".zhh.vld";
		// 移除所有事件委托, 以便重新添加
		form.off(namespace);
		// 绑定事件
		$.each(zv.bind, function() {
			if (this.on == "input") {
				if (formOpts.oninput != false) {
					var change = /msie/i.test(navigator.userAgent) ? "propertychange" : "input";
					form.on(change + namespace, this.selector, this.fn);
				}
			} else {
				form.on(this.on + namespace, this.selector, this.fn);
			}
		});
	};


	/**
	 * 校验FORM表单
	 * callback回调参数说明:
	 * passed(boolean) FORM表单校验是否通过
	 * text(String)    校验不通过时的错误原因(校验通过时为空)
	 * list([json])    每一个表单行的校验校验结果, json内容见vldinput的回调参数说明
	 */
	// formOpts = { init:true,oninput:true,focus:true,tips:true,disabled:false }
	var vldform = function(target, options, callback) {
		if ($.isFunction(target)) { callback = target; options = target = undefined; }
		else if ($.isPlainObject(target)) { callback = options; options = target; target = undefined; }
		else if ($.isFunction(options)) { callback = options; options = undefined; }

		var passed = true, list = [], focus;
		var $form = $(this);
		var formOpts = $.extend(true, {}, zv.fn.getNodeOptions($form), options);
		if (formOpts.disabled != true) {
			var fn = function(e) {
				// 如果校验被禁用, 或者target没有配置data-vld, 就不会有e.target
				if (e.target) { list.push(e); }
				// 只要有一条校验失败, 整个表单就不通过
				if (!e.passed) {
					passed = false;
					if (!focus) { focus = e; }
				}
			};
			if (formOpts.group == true) {
				vldline.call(this, target, formOpts, fn);
			} else {
				// 优先校验指定的目标
				var targets = $form.find("[data-vld]");
				if (target && lineOptions.failFast == true) {
					if ($.dt.isJqueryElement(target)) { target = target.get(0); }
					var temp = [];
					var group = $(target).parents(zv.selectors.line.join(","))
						.filter(function() {
							return zv.fn.getNodeOptions($(this)).group == true;
						});
					if (group.length) { temp.push(group.get(0)); }
					else { temp.push(target); }
					targets.each(function() {
						if (this !== target && this != temp[0]) { temp.push(this); }
					});
					targets = $(temp);
				}
				// 逐一校验line和target
				$form.find("[data-vld]").each(function() {
					var me = $(this);
					if (me.is(zv.selectors.target.join(","))) {
						vldinput.call(this, formOpts, fn);
					} else if (me.is(zv.selectors.line.join(",")) && zv.fn.getNodeOptions(me).group == true) {
						vldline.call(this, target, formOpts, fn);
					}
					if (!passed && formOpts.failFast == true) { return false; }
				});
			}
		}
		if (!passed) { // 未全部通过
			// 设置焦点为第1个校验失败的目标
			// 如果有target则 不能设置焦点, 因为只有提交时校验才需要设置焦点, 有target则是change事件导致的校验
			if(!target && formOpts.focus != false && focus && focus.target.is(":visible")) {
				try { focus.target.focus().select(); } catch(ignore) {} // 设置焦点
			}
		}
		var texts = [];
		$.each(list, function() {
			if (!this.passed && this.text) { texts.push(this.text); }
		});
		var text = texts.length == 0 ? undefined : texts.join(zv.locals[zv.lang]["{separator}"]);
		$.isFunction(callback) && callback.call(this, { passed:passed, list:list, focus:focus, text:text });
	};
	var vldline = function(target, options, callback) {
		if ($.isFunction(target)) { callback = target; options = target = undefined; }
		else if ($.isPlainObject(target)) { callback = options; options = target; target = undefined; }
		else if ($.isFunction(options)) { callback = options; options = undefined; }

		var self = this;
		var $line = $(this);
		var $form = $line.closest("form");

		// { group:true, tips:true, disabled:false }
		var formOptions = zv.fn.getNodeOptions($form);
		var lineOptions = $.extend(true, {}, formOptions, zv.fn.getNodeOptions($line), options);
		if (formOptions.disabled == true || lineOptions.disabled == true) {
			$.isFunction(callback) && callback.call(this, { passed:true });
			return;
		}

		var passed = true, succ = undefined, text = [];

		// 优先校验指定的目标
		var targets = $line.find(zv.selectors.target.join(","));
		if (target && lineOptions.failFast == true) {
			if ($.dt.isJqueryElement(target)) { target = target.get(0); }
			var temp = [target];
			targets.each(function() {
				if (this !== target) { temp.push(this); }
			});
			targets = $(temp);
		}
		targets.each(function() {
			var me = $(this);
			// { label:'string', disabled:false, rules:[ {required:true}, ... ] }
			var targetOpts = $.extend(true, {}, lineOptions, zv.fn.getTargetOptions(me), options);
			if (targetOpts.disabled == true) {
				$.isFunction(callback) && callback.call(this, { passed:true });
				return;
			}
			targetOpts.tips = false; // 设置为false,_vldinput不处理tips, 由vldline来处理
			_vldinput.call(self, me, $line, targetOpts, function(e) {
				if (!e.passed) {
					passed = false;
					succ = false;
					if (e.text) { text.push(e.text); }
				} else if (e.succ) {
					succ = true;
				}
				$.isFunction(callback) && callback.call(this, e);
			});
			if (!passed && lineOptions.failFast == true) { return false; }
		});
		if (lineOptions.tips != false) {
			var event = { passed:passed, succ:succ, line:$line};
			if (passed) {
				zv.fn.setTipsPassed(event);
			} else {
				zv.fn.setTipsError(event, text);
			}
		}
	};
	/**
	 * 校验输入框
	 * callback回调参数说明:
	 * passed(boolean)       表单行校验是否通过
	 * rule(Object)          导致校验失败的规则
	 * target(jQuery Object) 校验目标, 即输入框
	 * value(String)         校验目标(输入框)的值
	 * text(String)          校验不通过时的错误原因(校验通过时为空)
	 */
	var vldinput = function(options, callback) {
		if ($.isFunction(options)) { callback = options; options = undefined; }

		var self = this;
		var $target = $(this);
		var group = $(this).parents(zv.selectors.line.join(",")).filter("[data-vld]");
		if (group.length && zv.fn.getNodeOptions(group).group == true) {
			// 组合校验由vldline执行
			$.isFunction(callback) && callback.call(this, { passed:true });
			return;
		}
		var $form = $target.closest("form");
		var $line = $target.closest(zv.selectors.line.join(","));

		// { {$:{label:'string', disabled:false}}, {required:true}, ... }
		var formOptions = zv.fn.getNodeOptions($form);
		var targetOpts = $.extend(true, {}, formOptions, zv.fn.getTargetOptions($target), options);
		if (formOptions.disabled == true || targetOpts.disabled == true) {
			$.isFunction(callback) && callback.call(this, { passed:true });
			return;
		}

		_vldinput.call(self, $target, $line, targetOpts, callback);
	};
	var _vldinput = function($target, $line, options, callback) {
		var self = this;

		var rules = options.rules;
		if (options.disabled || !rules || !rules.length) {
			$.isFunction(callback) && callback.call(self, { passed:true });
			return;
		}

		var vc = zv.fn.isValueChanged($target);
		if (!vc.changed) { // 未改变
			var event = $target.data(zv.keys.LAST_RESULT);
			if (event) { // 取之前的校验结果回调
				$.isFunction(callback) && callback.call(self, event);
				return;
			}
		}

		var label = zv.fn.findLabelText($target, options);

		var value = vc.value;

		// passed:是否通过, succ:是否成功, 区别是succ只有在真正通过一项规则时才设置为成功
		// 例如: 一个非必填的文本框, 值为空, 则passed=true, succ=undefined, 此时判断succ==true才显示成功图标
		// 否则一个空文本框显示成功图标显得很奇怪
		var event = { passed:true, succ:undefined };

		$.each(rules, function() {
			event = { target:$target, line:$line, value:value, label:label };
			var unmatched = true;
			for (var type in this) {

				var fn = zv.rules.get(type);
				if (!$.isFunction(fn)) { continue; }
				unmatched = false;

				event.rule = $.extend(true, { type:type }, this);

				// 执行校验函数(调用前加上config参数)
				var result = fn.call(self, event, options);
				// 明确的return false, return json即为校验不通过
				// 没有return; 有return; 或return true; 都认为校验通过
				// 校验通过就将错误提示隐藏掉, 但强制显示的除外(如密码强度提示)
				if (result === true || result == undefined) { // 通过
					event.passed = true;
					if (event.value.length > 0) { event.succ = true; }
				} else { // 不通过
					// 0. data = { "{text}":text }
					// 1. data = false
					// 2. data = "key"
					// 3. data = { key:value }
					// 4. data = { "{key}":key, x:xvalue, y:yvalue }
					var text;
					if ($.isPlainObject(result) && ("{text}" in result)) {
						text = result["{text}"];
					} else {
						text = zv.fn.createTipsText(event, result);
					}
					event.text = text;
					if (options.tips != false) {
						zv.fn.setTipsError(event, text);
					}
					// 校验不通过返回false, 就不会执行下一条规则了
					event.passed = false;
					event.succ = false;
				}
				// 缓存校验结果
				$target.data(zv.keys.LAST_RESULT, event);
				if (!event.passed) {
					// 只要有一条规则校验不通过, 就不再校验这个$target了
					return false;
				}
			}
			if (unmatched) { // 一个规则也没匹配上, 输出错误日志
				event.passed = true; // 配置错误, 则规则不生效, 还是应该通过校验
				var name = $target.attr("name") ? "'" + $target.attr("name") + "' " : "";
				$.log.debug(name + "rule handler not found. " + JSON.stringify(this));
			}
		});

		if (!event.passed) { // 校验未通过
			if (zv.classes["line-error"]) { $line.addClass(zv.classes["line-error"]); }
			if (zv.classes["line-passed"]) { $line.removeClass(zv.classes["line-passed"]); }
		} else { // 校验通过
			if (options.tips != false) {
				zv.fn.setTipsPassed(event);
			}
			if (zv.classes["line-error"]) { $line.removeClass(zv.classes["line-error"]); }
			if (zv.classes["line-passed"]) { $line.addClass(zv.classes["line-passed"]); }
		}
		delete event.rule; // event.rule是临时变量, 不能作为回调参数
		$.isFunction(callback) && callback.call(self, event);
	};

	var zv = $.fn.zhhvalidate.defaults = {
		locals: {
			"en": { "{default}": "{label} validation is not passed.", "{separator}":"; " },
			"zh-CN": { "{default}": "{label}输入有误", "{separator}":"、" }
		},
		lang: "zh-CN",
		// 规则的获取/新增/修改/删除
		rules: rules,
		keys: {
			LAST_VALUE: "VLD_LAST_VALUE",
			LAST_RESULT: "VLD_LAST_RESULT",
			TIPS_DEF_HTML: "VLD_TIPS_DEF_HTML"
		},
		classes: {
			"line-error": "vld-error",
			"line-passed": null,
			"tips-error": "vld-error",
			"tips-passed": "vld-passed",
			"tips-loading": "vld-loading"
		},
		selectors: {
			target: ["input[data-vld], textarea[data-vld], select[data-vld]"],
			line: [".vld-line", ".form-group"], // form-group是bootstrap的表单行
			label: [".vld-label", ".control-label"], // control-label是bootstrap的标签
			tips: [".vld-tips"]
		},
		bind: [
			{ on:"input", selector:"input[data-vld], textarea[data-vld]", 
				fn:function() {
					var group = $(this).parents(zv.selectors.line.join(",") + ",form")
						.filter(function() {
							return zv.fn.getNodeOptions($(this)).group == true;
						});
					if (group.length) {
						group.zhhvalidate(this);
					} else {
						$(this).zhhvalidate();
					}
				} },
			{ on:"click", selector:"input[type=radio], input[type=checkbox]", 
				fn:function() {
					$(this).zhhvalidate();
				} },
			{ on:"change", selector:"input[type=radio], input[type=checkbox], select", 
				fn:function() {
					$(this).zhhvalidate();
				} },
			// 提交按钮
			{ on:"click", selector:"a.vld-submit, button.vld-submit, input[type=button].vld-submit", 
				fn:function(e) {
					e.preventDefault();
					var $form = $(this).closest("form");
					$form.zhhvalidate(function(e) {
						if (e.passed) { $form.submit(); }
					});
				} }
		],
		fn: {
			// 查找校验目标的提示文本
			// 查找顺序:
			// 1. data-vld="{ label:'xxx', ... }"
			// 2. class="vld-label"标签中的文本
			// 3. class="control-label"标签中的文本
			findLabelText: function($target, options) {
				if (!options) { options = zv.fn.getTargetOptions($target); }
				if (options.label) {
					return options.label;
				}
				var $line = $target.closest(zv.selectors.line.join(","));
				var text = $line.find(zv.selectors.label.join(",")).eq(0).text();
				// 替换掉前面的*和后面的:
				// 如 * 用户名 : 替换为用户名
				return text && $.trim(text).replace(/(^\*\s*|\s*[:：]$)/g, "");
			},
			findTargets: function($target) {
				if($target.attr("name") && $target.is("[type=checkbox],[type=radio]")) {
					// 如果是checkbox/radio
					var name = $target.attr("name");
					var type = $target.attr("type");
					var form = $target.closest("form");
					$target = form.find("[name='" + name + "'][type='" + type + "'][data-vld]");
				}
				return $target;
			},
			// data-options选项
			// data-vld="{ label:'xxx',disabled:false, rules:[{required:true},{regexp:'ascii'},{regexp:'illegal-char'},...] }"
			// data-vld="[ {$:{label:'xxx',disabled:false}}, {required:true},{regexp:'ascii'},{regexp:'illegal-char'},... ]"
			// data-vld="[ {required:true},{regexp:'ascii'},{regexp:'illegal-char'},... ]"
			// data-vld="{required:true,regexp:'illegal-char',...}"
			getTargetOptions: function($target) {
				$target = zv.fn.findTargets($target);
				var zo = $target.zoptions("vld");
				return $.zhh.parseOptions(zo, "rules");
			},
			setTargetOptions: function($target, options) {
				zv.fn.findTargets($target).zoptions("vld", options);
			},
			// data-options选项
			// <div class="vld-line" data-vld="{ group:true,failFast:false,tips:true,disabled:false }">
			// <form data-vld="{ init:true,oninput:true,focus:true,failFast:false,tips:true,disabled:false }">
			getNodeOptions: function($node) {
				return $node.zoptions("vld");
			},
			// 获取校验的值, 即输入框的值
			getValue: function($target) {
				if($target.is("[type=checkbox],[type=radio]")) {
					// 如果是checkbox/radio
					var name = $target.attr("name");
					if (name) {
						var type = $target.attr("type");
						var form = $target.closest("form");
						return form.find("[name='" + name + "'][type='" + type + "']:checked").val();
					} else { // 没有名字
						return $target.filter(":checked").val();
					}
				} else {
					// 获取校验的值, 即输入框的值
					var value = $target.val();
					// 是否截掉前后空格, data-trim=true
					if ($target.data("trim") == "true" || $target.data("trim") == true) {
						value = $.trim(value);
					}
					// 考虑到有些情况会在文本框里填写输入提示
					// 如: <input value="用户名/手机号码/邮箱地址" />
					// 配置data-placeholder="用户名/手机号码/邮箱地址"
					if (value === $target.data("placeholder")) { value = ""; }
					return value;
				}
			},
			// IE8修改任何属性都会触发propertychange, 例如addClass(), 有时会导致内存溢出
			// 因此需要记录原来的值与目前的值对比
			isValueChanged: function($target) {
				var value = zv.fn.getValue($target);
				var last = $target.data(zv.keys.LAST_VALUE);
				if (value == last) {
					return { value:value, changed:false };
				} else {
					$target.data(zv.keys.LAST_VALUE, value === undefined ? null : value);
					return { value:value, older:last, changed:true };
				}
			},
			clear: function($target) {
				$target.removeData(zv.keys.LAST_VALUE);
				var $line = $target.closest(zv.selectors.line.join(","));
				$line.removeClass("vld-error");
				$.each(["line-passed", "line-error"], function(i, name) {
					if (zv.classes[name]) { $line.removeClass(zv.classes[name]); }
				});
				var $tips = zv.fn.findTipsNode($line);
				$.each(["tips-passed", "tips-error", "tips-loading"], function(i, name) {
					if (zv.classes[name]) { $tips.removeClass(zv.classes[name]); }
				});
				var html = $tips.data(zv.keys.TIPS_DEF_HTML);
				if (html && html != $tips.html()) {
					$tips.html(html);
				}
			},
			// 1. data = false
			// 2. data = "key"
			// 3. data = { key:value }
			// 4. data = { "{key}":key, x:xvalue, y:yvalue }
			createTipsText: function(e, data) {
				var local = zv.locals[zv.lang];
				var keys = [e.rule.type], args = { label:e.label };
				if (typeof(data) == "string") { // 2
					keys.push(data);
				} else if ($.isPlainObject(data)) {
					if ("{key}" in data) { // 4
						var k = data["{key}"];
						if (k) { keys.push(k) }
						delete data["{key}"];
					} else { // 3
						for (var k in data) {
							// 只取一个
							if (k) { keys.push(k); break; }
						}
					}
					$.extend(args, data);
				}
				var msg = e.rule.text || local[keys.join("-")];
				if (!msg) {
					msg = local[e.rule.type];
				}
				if (!msg) {
					msg = local["{default}"];
				}
				if (!msg) {
					msg = e.rule.type + " error";
				}
				return $.zhh.format(msg, args);
			},
			findTipsNode: function($line) {
				var $tips = $line.find(zv.selectors.tips.join(","));
				if ($tips.data(zv.keys.TIPS_DEF_HTML) == undefined) {
					$tips.data(zv.keys.TIPS_DEF_HTML, $tips.html());
				}
				return $tips;
			},
			setTipsLoading: function(e, text) {
				var $tips = zv.fn.findTipsNode(e.line);
				$tips.text(text);
				$.each(["tips-error", "tips-passed"], function(i, name) {
					if (zv.classes[name]) { $tips.removeClass(zv.classes[name]); }
				});
				if (zv.classes["tips-loading"]) { $tips.addClass(zv.classes["tips-loading"]); }
			},
			setTipsError: function(e, text) { // text:string|Array
				var $tips = zv.fn.findTipsNode(e.line);
				$tips.html($.isArray(text) ? text.join(zv.locals[zv.lang]["{separator}"]) : text);
				$.each(["tips-passed", "tips-loading"], function(i, name) {
					if (zv.classes[name]) { $tips.removeClass(zv.classes[name]); }
				});
				if (zv.classes["tips-error"]) { $tips.addClass(zv.classes["tips-error"]); }
			},
			setTipsPassed: function(e) {
				var $tips = zv.fn.findTipsNode(e.line);
				$tips.text("");
				$.each(["tips-passed", "tips-error", "tips-loading"], function(i, name) {
					if (zv.classes[name]) { $tips.removeClass(zv.classes[name]); }
				});
				if (e.succ) { // 只有e.succ才显示图标, e.passed不显示图标
					if (zv.classes["tips-passed"]) { $tips.addClass(zv.classes["tips-passed"]); }
				}
			},
			// fields='name'
			// fields={paramName:'fieldName'}
			// fields=['fieldName1','fieldName2',{paramName:'fieldName3'}]
			getFormFields: function(form, fields) {
				if (!$.isArray(fields)) { fields = [fields]; }
				var result = {};
				$.each(fields, function(i, field) {
					if ($.isPlainObject(field)) {
						for (var key in field) {
							result[key] = form.find("[name='"+field[key]+"']").val();
						}
					} else {
						result[field] = form.find("[name='"+field+"']").val();
					}
				});
				return result;
			}
		},
		methods: {
			// 清除之前的校验缓存和校验提示
			clear: function() {
				return this.each(function(i) {
					var me = $(this);
					if (me.is("form")) {
						me.find(zv.selectors.target.join(",")).each(function() {
							zv.fn.clear($(this));
						});
					} else {
						zv.fn.clear(me);
					}
				});
			},
			// 禁用校验
			disable: function() {
				return this.each(function(i) {
					var me = $(this);
					if (me.is("form")) {
						var options = me.zoptions("vld");
						options.disabled = true;
					} else {
						var options = zv.fn.getTargetOptions(me);
						options.disabled = true;
						zv.fn.setTargetOptions(me, options);
					}
					zv.methods.clear.call(me);
				});
			},
			// 启用校验
			enable: function(enableChildren) {
				return this.each(function(i) {
					var me = $(this);
					if (me.is("form")) {
						var options = me.zoptions("vld");
						options.disabled = false;
						if (enableChildren) {
							me.find(zv.selectors.target.join(",")).each(function() {
								zv.methods.enable.call($(this));
							});
						}
					} else {
						var options = zv.fn.getTargetOptions(me);
						options.disabled = false;
						zv.fn.setTargetOptions(me, options);
					}
				});
			}
		}
	};

})(jQuery);


// 校验规则
(function(zv) {
	// 必填校验, 表示该字段必须输入, 如果为空则显示错误
	// 单选框复选框, 表示该字段必须选择至少一项, 如果未选择则显示错误
	zv.rules.put("required", function(e) {
		if (e.rule.required == false) {
			return true;
		}
		if (e.target.is("input[type=radio], input[type=checkbox]")) {
			if (e.value == undefined || e.value.length <= 0) { return "select"; }
		} else if (e.target.is("select")) {
			if (e.value == undefined || e.value.length <= 0) { return "select"; }
		} else if(e.value == undefined || e.value.length <= 0) {
			return "input";
		}
	});
	// 长度校验, 表示该字段必须符合长度要求, 符合为通过, 不符合则显示错误
	// { length:{min:n,max:m} }
	// { length:[n,m] }
	// min=最小值, max=最大值
	zv.rules.put("length", function(e) {
		var len = e.value.length;
		if (len <= 0) { return true; }
		var min = parseInt(e.rule.length.min || e.rule.length[0]),
			max = parseInt(e.rule.length.max || e.rule.length[1]);
		if (!isNaN(min) && len < min) { // 有min
			return { min:min };
		}
		if (!isNaN(max) && len > max) { // 有max
			return { max:max };
		}
	});
	// 数字校验, 表示该字段必须是数字, 符合为通过, 不符合则显示错误
	// { number:true|false, ... }
	// { number:{min:n,max:m}, sign:true|false, decimal:n }
	// { number:[n,m], ... }
	// { number:{sign:true|false, decimal:n} }
	// min=最小值, max=最大值
	// sign=是否允许前面的+-号, 默认不允许
	// decimal=小数位数, 默认为0
	zv.rules.put("number", function(e) {
		var len = e.value.length;
		if (len <= 0) { return true; }
		if (e.rule.number === false || e.rule.number === "false") {
			// 不能是数字, 其他选项无效, 直接返回
			return /^[+-]?\d+(\.\d+)?$/.test(e.value) ? "false" : true;
		} else if (e.rule.number === true || typeof(e.rule.number) == "string") {
			e.rule.number = {};
		}
		// 不是数字
		if (!/^[+-]?\d+(\.\d+)?$/.test(e.value)) {
			return "error"; // 不是数字
		}
		var sign = e.rule.sign || e.rule.number.sign;
		if (sign != true && /^[+-]/.test(e.value)) {
			return "sign"; // 不允许有符号, 但是有符号
		}
		var decimal = e.rule.decimal || e.rule.number.decimal;
		if (!decimal) { // 0或undefined, 不允许小数
			if (e.value.indexOf(".") >= 0) {
				return "integer";
			}
		} else { // 允许小数
			var index = e.value.lastIndexOf(".");
			if (index >= 0 && e.value.length - index - 1 > decimal) {
				return { decimal:decimal };
			}
		}
		var value = parseFloat(e.value);
		var min = parseInt(e.rule.number.min || e.rule.number[0]),
			max = parseInt(e.rule.number.max || e.rule.number[1]);
		if (!isNaN(min) && value < min) { // 有min
			return { min:min };
		}
		if (!isNaN(max) && value > max) { // 有max
			return { max:max };
		}
	});
	// 相等校验, 表示该字段与另一字段必须相等, 相等为通过, 不相等则显示错误
	// { compare:selector,reverse:true|false  }
	// reverse=true表示反转判断结果, 如果相等就提示错误
	zv.rules.put("compare", function(e) {
		if(e.value.length <= 0) { return true; }
		var match = e.rule.reverse != true;
		var $form = e.target.closest("form");
		var to = e.rule.compare;
		var $to = $form.find(to);
		var totext = zv.fn.findLabelText($to);
		var tovalue = zv.fn.getValue($to);
		if((e.value == tovalue) != match) {
			if (match) {
				// {label}与{target}不符
				return { "{key}":"equal", target:totext };
			} else {
				// {label}不能与{target}相同
				return { "{key}":"notequal", target:totext };
			}
		}
	});

	$.extend(zv.locals["zh-CN"], {

		// 必填校验, 表示该字段必须输入, 如果为空则显示错误
		// 单选框复选框, 表示该字段必须选择至少一项, 如果未选择则显示错误
		"required-input":"{label}不能为空",
		"required-select":"{label}请选择一项",

		// min=最小值, max=最大值
		"length-min":"{label}长度不能小于{min}位",
		"length-max":"{label}长度不能大于{max}位",

		// 数字校验, 表示该字段必须是数字, 符合为通过, 不符合则显示错误
		// min=最小值, max=最大值
		// sign=是否允许前面的+-号, 默认不允许
		// decimal=小数位数, 默认为0
		"number-false":"{label}不能是数字",
		"number-error":"{label}请输入数字",
		"number-integer":"{label}请输入整数",
		"number-decimal":"{label}小数位数不能大于{decimal}位",
		"number-sign":"{label}不允许输入+-号",
		"number-min":"{label}不能小于{min}",
		"number-max":"{label}不能大于{max}",

		// 相等校验, 表示该字段与另一字段必须相等, 相等为通过, 不相等则显示错误
		// reverse=true表示反转判断结果, 如果相等就提示错误
		"compare-equal":"{label}与{target}不符",
		"compare-notequal":"{label}不能与{target}相同"
	});
})(jQuery.fn.zhhvalidate.defaults);


// 正则表达式校验规则
(function(zv) {
	// 正则表达式校验, 表示该字段必须匹配正则, 匹配为通过, 不匹配则显示错误
	// { regexp:string,reverse:true|false }
	// reverse=true表示反转判断结果, 如果匹配就提示错误
	zv.rules.put("regexp", function(e) {
		if(e.value.length <= 0) { return true; }
		var text = e.rule.regexp;
		// 符合命名规范的文本, 判定为指向DEF.regexp的名字
		// 如url=zv.regexp.url, email=zv.regexp.email
		// 命名规范:以字母数字下划线开头和结尾,中间可以有.和-但不能连续
		if (typeof(text) == "string" && zv.regexp[text]) { // 预定义的正则表达式
			var regexp = zv.regexp[text];
			var match = true;
			// zv.regexp.mobile = /^1[0-9]{10}$/;
			// zv.regexp.illegal = { value:/[!@#$%^&*,.\/\\]/, reverse:true };
			if (regexp.constructor != RegExp) {
				match = regexp.reverse != true;
				regexp = regexp.value;
			}
			if(regexp.test(e.value) != match) {
				return text;
			}
		} else { // 根据配置生成正则表达式
			var regexp = text.constructor == RegExp ? text : new RegExp(text);
			var match = e.rule.reverse != true;
			if(regexp.test(e.value) != match) {
				return false; // 无法做到通用提示, 只能提示格式有误
			}
		}
	});

	var URL = new RegExp(
	"^("
		+ "(https?://)?" // 以http或https开头,也可以没有
		+ "("
		+     "[0-9]{1,3}([\\.][0-9]{1,3}){3}" // 4段的IP地址, 如192.168.88.99
		+ "|"
		+     "([^\\./\\?&@#]+[\\.])+" // 域名可以有任意多段, www.open.wo, baidu
		+     "[^\\./\\?&@#]{2,4}" // 后缀为2-4位, 如com, cn, info等
		+ ")"
		+ "(:[0-9]{2,})?" // 端口, 如:80, :443, :8080, 这一段可有可无
		+ "([/\\?#].*)?" // 网页地址或参数, 可为任意字符, 也可以没有
	+ ")?$", // 空字符串也可以通过校验
	"i"); // i:不区分大小写

	// 预定义正则表达式
	zv.regexp = {
		url: URL,
		ascii: /^[\x00-\x7F]*$/, // 不支持中文
		mobile: /^1[0-9]{10}$/,
		email: /^[^\s@]+@([^\s@\.]+\.)+[^\s@\.]{2,4}$/,
		// 不允许输入非法字符
		// 正则表达式校验, 表示该字段必须匹配正则, 匹配为通过, 不匹配则显示错误
		// reverse:true表示反转判断结果, 如果匹配就提示错误
		"illegal-char": { value:/[!@#$%^&*,.\/\\]/, reverse:true }
	};

	$.extend(zv.locals["zh-CN"], {
		"regexp": "{label}格式有误", // 正则表达式默认错误提示
		"regexp-url": "{label}格式有误",
		"regexp-mobile": "{label}格式有误",
		"regexp-email": "{label}格式有误",
		"regexp-ascii": "{label}只支持英文字符",
		"regexp-illegal-char": "{label}不支持!@#$%^&*,./\等特殊字符"
	});
})(jQuery.fn.zhhvalidate.defaults);



// AJAX远程校验规则
(function(zv) {
	// 通过ajax向服务器请求校验
	// { ajax:'url.do', fields:fields, options:{ type:GET|POST, dataType:json|html|text, ...} }
	// fields='name'
	// fields={fieldName:'paramName'}
	// fields=['type','name',{fieldName:'paramName'}]
	zv.rules.put("ajax", function(e) {
		if(e.value.length <= 0) { return true; }

		var self = this;
		var result = true;

		// 正在加载
		var local = zv.locals[zv.lang];
		zv.fn.setTipsLoading(e, local["ajax-loading"]);
		var error = function() { // 报错
			result = "error";
		};
		var succ = function(data) {
			var json = zv.ajax.parse.call(self, e, data);
			if(!json || json.passed === undefined) {
				// 没有获取到响应报文或者响应报文不符合格式
				error();
			} else if(json.passed) { // 校验通过
				result = true;
			} else { // 校验不通过
				if (json.text) {
					result = { "{text}":json.text };
				} else {
					result = "fail";
				}
			}
		};

		var params = {};
		if (e.rule.fields) {
			params = zv.fn.getFormFields(e.target.closest("form"), e.rule.fields);
		} else {
			params[e.target.attr("name") || "vlaue"] = e.value;
		}
		// 请求参数
		var options = $.extend({}, zv.ajax.options, e.rule.options, { url: e.rule.ajax, data: params });
		// 发送请求前修改参数的机会
		zv.ajax.prepare.call(self, e, options);
		// 向服务器发送请求
		$.ajax($.extend(options, { success:succ, error:error, async:false }));
		return result;
	});
	// 准备向服务器发送请求前的处理
	var prepare = function(e, options) {
	};
	// 解析AJAX请求的响应报文
	var parse = function(e, json) {
		if (!json || $.isPlainObject(json)) { return json; }
		if(json === true || json == "true" || json == "1") { // 校验通过
			return { passed:true };
		} else if(json === false || json == "false" || json == "0") { // 校验不通过
			return { passed:false };
		} else if(json.length > 200) {
			// 错误信息太长, 可能是后台报错, 打印了很多堆栈信息
			return false; // 报错了
		} else { // 校验不通过, 返回了具体错误原因
			return { passed:false, text:json };
		}
	};
	zv.ajax = {
		url:"ajaxvalidate.do",
		// 准备向服务器发送请求前的处理
		prepare:prepare,
		// 解析AJAX请求的响应报文
		parse:parse,
		options: { type:'POST', dataType:'json' }
	};
	$.extend(zv.locals["zh-CN"], {
		"ajax-error": "{label}校验出错",
		"ajax-fail": "{label}校验不通过",
		// AJAX的加载中提示
		"ajax-loading": "正在校验..."
	});
})(jQuery.fn.zhhvalidate.defaults);


// 密码强度校验规则
(function(zv) {

	// 密码级别: 1.极弱|2.一般|3.较强|4.极强
	var pwdlevel = function(val) {
		if(val.length < zv.pwd.minlength) { return 1; } // 小于6位, 弱爆了
		var count = 0,
			letter = /[a-zA-Z]+/, // 字母
			number = /[0-9]+/, // 数字
			other = /[^0-9a-zA-Z]+/; // 符号
		$.each([letter, number, other], function(i) {
			if (this.test(val)) { count ++; }
		});
		// 有3种类型且长度超过12位且符号超过4位为极强
		if (count >= 3 && val.length >= 12) {
			// 统计每一种类型的字符个数
			var list = [0, 0, 0];
			$.each(val.split(""), function(i, c) {
				$.each([letter, number, other], function(j) {
					if (this.test(c)) { list[j]++; }
				});
			});
			if (list[2] >= 4) { // 符号超过4位
				return 4;
			}
		}
		// 有3种为较强, 2种为一般, 1种为极弱
		return count >= 3 ? 3 : count >= 2 ? 2 : 1;
	};
	zv.pwd = {
		// 密码最小长度
		minlength: 6,
		// 密码最低级别
		minlevel: 2,
		// 检查密码级别的函数, 返回值: 1.极弱|2.一般|3.较强|4.极强
		pwdlevel:pwdlevel,
		// 密码级别, 不可轻易修改, 如果需要修改, css文件也要跟着改
		levels: { 1:"weak", 2:"medium", 3:"strong", 4:"best" }
	};
	// 密码强度校验, 必须密码达到指定级别, 否则提示错误
	// { pwdlevel:true|1~4|weak|medium|strong|best }
	zv.rules.put("pwdlevel", function(e) {
		if(e.value.length <= 0) { return true; }
		var level = e.rule.pwdlevel;
		if (typeof(level) == "boolean") { level = zv.pwd.minlevel; }
		// 如果不是数字, 转换为数字
		if (/[^0-9]/.test(level)) {
			var levels = zv.pwd.levels;
			var text = level; level = 0;
			for (var key in levels) {
				if (levels[key] == text) { level = key; }
			}
		}
		// 如果小于1或大于4, 默认为2
		if (level < 1 || level > 4) { level = zv.pwd.minlevel; }
		if(zv.pwd.pwdlevel(e.value) < level * 1) {
			return zv.pwd.levels[level];
		}
	});
	$.extend(zv.locals["zh-CN"], {
		// 密码级别  { 1:"weak", 2:"medium", 3:"strong", 4:"best" }
		"pwdlevel-weak":  "{label}必须包含字母/数字/特殊字符的任意一种",
		"pwdlevel-medium":"{label}必须包含字母/数字/特殊字符的任意两种",
		"pwdlevel-strong":"{label}必须包含字母/数字/特殊字符",
		"pwdlevel-best":  "{label}必须包含字母/数字/特殊字符且至少12位且特殊字符超过4位"
	});
})(jQuery.fn.zhhvalidate.defaults);

